namespace Hack.Decompiling.PEA.PE
{
  using System;
  using System.IO;
  using System.Diagnostics;
  using System.Collections.Generic;
  using System.Text;

  /// <summary>
  /// Based relocation types.
  /// </summary>
  public enum RelocationTypes
  {
    ABSOLUTE = 0,
    HIGH = 1,
    LOW = 2,
    HIGHLOW = 3,
    HIGHADJ = 4,
    MIPS_JMPADDR = 5,
    MIPS_JMPADDR16 = 9,
    IA64_IMM64 = 9,
    DIR64 = 10,
  }
  /// <summary>
  /// Based relocation format.
  /// </summary>
  public struct BaseRelocation
  {
    #region Statics
    public static readonly BaseRelocation Terminator = it();
    public static UInt16 GetRelocationOffset(UInt16 typeOffset)
    {
      return (UInt16)(typeOffset & RelocationOffsetMask);
    }
    public static RelocationTypes GetRelocationType(UInt16 typeOffset)
    {
      return (RelocationTypes)(typeOffset >> 12);
    }
    public static BaseRelocation it()
    {
      return new BaseRelocation();
    }
    public static BaseRelocation from(BinaryReader reader)
    {
      return it().Read(reader);
    }
    #endregion
    #region Consts
    public const int SizeOfBaseRelocation = 8;
    public const UInt32 TerminatorAddress = 0;
    public const UInt16 RelocationOffsetMask = 0x0FFF;
    public const UInt16 RelocationTypeMask = 0xF000;
    #endregion
    #region Fields
    public UInt32 VirtualAddress;
    public UInt32 SizeOfBlock;
    public UInt16[] TypeOffsets;
    #endregion
    #region Properties
    public UInt32 Count
    {
      get
      {
        return this.SizeOfBlock >= SizeOfBaseRelocation ?
          (SizeOfBlock - SizeOfBaseRelocation) / 2 :
          0;
      }
      set
      {
        this.SizeOfBlock = SizeOfBaseRelocation + 2 * value;
      }
    }
    public bool IsValid
    {
      get
      {
        return this.VirtualAddress != TerminatorAddress;
      }
    }
    public bool HasTypeOffsets
    {
      get
      {
        return this.TypeOffsets != null && this.TypeOffsets.Length > 0;
      }
    }
    #endregion
    #region Constructors
    #endregion
    #region Methods
    #region Read/Write
    public BaseRelocation Read(BinaryReader reader)
    {
      if (reader != null)
      {
        this.VirtualAddress = Utils.Read32(reader);

        if (this.IsValid)
        {
          this.SizeOfBlock = Utils.Read32(reader);
          this.TypeOffsets = this.ReadRelocations(reader, this.Count);
        }
      }
      return this;
    }
    public BaseRelocation Write(BinaryWriter writer)
    {
      if (writer != null)
      {
        if (this.IsValid)
        {
          Utils.Write32(writer,this.VirtualAddress);
          Utils.Write32(writer,this.SizeOfBlock);
          this.WriteRelocations(writer,this.TypeOffsets);
        }
        else
        {
          Utils.Write32(writer, TerminatorAddress);
        }
      }
      return this;
    }
    #endregion
    #region Internals
    internal UInt16[] ReadRelocations(BinaryReader reader,UInt32 count)
    {
      UInt16[] entries = null;
      if (reader != null)
      {
        entries = new UInt16[count];
        for (UInt32 i = 0; i < count; i++)
        {
          entries[i] = Utils.Read16(reader);
        }
      }
      return entries;
    }
    internal UInt16[] WriteRelocations(BinaryWriter writer,UInt16[] entries)
    {
      if (writer != null && entries!=null)
      {
        for (UInt32 i = 0; i < entries.Length; i++)
        {
          Utils.Write16(writer,entries[i]);
        }
      }
      return entries;
    }
    #endregion
    #endregion
  }
  /// <summary>
  /// RelocationDirectory
  /// </summary>
  public struct RelocationDirectory:DataDirectory
  {
    #region Statics
    public static RelocationDirectory it()
    {
      return new RelocationDirectory();
    }
    public static RelocationDirectory from(BinaryReader reader,NTHeader header)
    {
      return (RelocationDirectory)it().Read(reader, header);
    }
    #endregion
    #region Consts
    #endregion
    #region Fields
    public BaseRelocation[] Relocations;
    #endregion
    #region Internal Fields
    internal ImageDataDirectory _Entry;
    #endregion
    #region Properties
    public ImageDataDirectory Entry
    {
      get
      {
        return this._Entry;
      }
      set
      {
        this._Entry = value;
      }
    }
    public bool HasRelocations
    {
      get
      {
        return this.Relocations != null && this.Relocations.Length > 0;
      }
    }
    #endregion
    #region Constructors
    #endregion
    #region Methods
    public DataDirectory Read(BinaryReader reader, NTHeader header)
    {
      if (reader != null)
      {
        this._Entry = header[DirectoryEntries.Relocation];

        Int64 current = Utils.GetPosition(reader);

        UInt32 start = header.GetFilePointer(this.Entry.VirtualAddress);

        if (start != 0 && Utils.SetPosition(reader, start))
        {
          this.Relocations = this.ReadRelocations(reader);

          Utils.SetPosition(reader, current);
        }
      }
      return this;
    }
    public DataDirectory Write(BinaryWriter writer, NTHeader header)
    {
      if (writer != null)
      {
        Int64 current = Utils.GetPosition(writer);

        UInt32 start = header.GetFilePointer(this.Entry.VirtualAddress);

        if (start != 0 && Utils.SetPosition(writer, start))
        {
          header[DirectoryEntries.Relocation] = this._Entry;

          this.WriteRelocations(writer, this.Relocations);

          Utils.SetPosition(writer, current);
        }
      }
      return this;
    }
    #region Internals
    internal BaseRelocation[] ReadRelocations(BinaryReader reader)
    {
      BaseRelocation[] relocations = null;
      if (reader != null)
      {
        List<BaseRelocation> relocationList = new List<BaseRelocation>();
        while (true)
        {
          BaseRelocation relocation = BaseRelocation.from(reader);
          if (relocation.IsValid)
          {
            relocationList.Add(relocation);
          }
          else
          {
            break;
          }
        }
        if (relocationList.Count > 0)
        {
          relocations = relocationList.ToArray();
        }
      }

      return relocations;
    }
    internal BaseRelocation[] WriteRelocations(BinaryWriter writer, BaseRelocation[] relocations)
    {
      if (writer != null && relocations != null)
      {
        for (UInt32 i = 0; i < relocations.Length; i++)
        {
          relocations[i].Write(writer);
        }
        BaseRelocation.Terminator.Write(writer);
      }

      return relocations;
    }
    #endregion
    #endregion
  }
}